//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.ScientificWorkflow.ManagementStudioControls.RegistryManagerModule
{
    using System.ComponentModel;
    using System.Text;
    using System.Windows;
    using ManagementStudioServices.Interfaces;
    using Microsoft.Research.ScientificWorkflow.ManagementStudioControls.Common;
    using Microsoft.Research.ScientificWorkflow.ManagementStudioModels;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities;
    using Microsoft.Research.ScientificWorkflow.TridentUtilities.Properties;
    using Microsoft.Research.ScientificWorkflow.UIDesigner.WpfComposite;
    using Microsoft.Practices.Unity;
    using SR = Microsoft.Research.DataLayer;
    using Microsoft.Research.Authentication;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Collections.Generic;
    using System;
    using Microsoft.Research.ScientificWorkflow.TridentModel;
    using System.Globalization;

    /// <summary>
    /// Workflow Catalog CenterPane Presenter
    /// </summary>
    public class UserCenterPanePresenter : IRegistryManagerCenterPanePresenter, INotifyPropertyChanged
    {
        private UserViewModel selectedModel;

        private UserViewModel editableUserModel;

        private IUnityContainer container;

        private IRegistryManagerService registryManagerService;

        private bool saveInProgress;

        /// <summary>
        /// Gets or sets the UpdateCommand command.
        /// </summary>
        /// <value>The browse category command.</value>
        public DelegateCommand<BaseRegMgrModel> UpdateCommand { get; set; }

        /// <summary>
        /// Gets or sets the save category changes command.
        /// </summary>
        /// <value>The save category changes command.</value>
        public DelegateCommand<object[]> SaveUserCommand { get; set; }

        /// <summary>
        /// Gets or sets the select root command.
        /// </summary>
        /// <value>The select root command.</value>
        public DelegateCommand<object> SelectRootCommand { get; set; }

        /// <summary>
        /// Gets or sets the cancel category changes command.
        /// </summary>
        /// <value>The cancel category changes command.</value>
        public DelegateCommand<object> CancelUserChangesCommand { get; set; }


        /// <summary>
        /// Gets or sets the delete category command.
        /// </summary>
        /// <value>The delete category command.</value>
        public DelegateCommand<object> DeleteUserCommand { get; set; }

        /// <summary>
        /// Gets or sets the Add User category command.
        /// </summary>
        /// <value>The adds category command.</value>
        public DelegateCommand<object> AddUserCommand { get; set; }

        /// <summary>
        /// Gets or sets the Add User category command.
        /// </summary>
        /// <value>The adds category command.</value>
        public DelegateCommand<object> AddUserCredentialCommand { get; set; }


        /// <summary>
        /// Gets or sets the remove User category command.
        /// </summary>
        /// <value>Remove category command.</value>
        public DelegateCommand<object> RemoveUserCredentialCommand { get; set; }

        /// <summary>
        /// Gets or sets the delete category command.
        /// </summary>
        /// <value>The delete category command.</value>
        public DelegateCommand<BaseRegMgrModel> DeleteCommand { get; set; }

        /// <summary>
        /// Selected Credentilas from the Crdentila list box
        /// </summary>
        public LogOnCredentialModel SelectedCredentials { get; set; }

        public UserCenterPanePresenter(IUnityContainer container, UserCatalogCenterPane contentView, IRegistryManagerService service)
        {
            this.container = container;
            contentView.DataContext = this;
            this.ContentView = contentView;
            this.registryManagerService = service;
            this.container = container;
            this.registryManagerService = service;
            this.SaveUserCommand = new DelegateCommand<object[]>(this.SaveUserChangesExecute);
            this.CancelUserChangesCommand = new DelegateCommand<object>(p => this.CancelUserChangesExecute());
            this.DeleteUserCommand = new DelegateCommand<object>(p => this.DeleteUserCommandExecute(), p => this.CanExecuteDelete());
            this.AddUserCommand = new DelegateCommand<object>(p => this.AddUserExecute());
            this.AddUserCredentialCommand = new DelegateCommand<object>(p => this.AddUserCredentialCommandExecute());
            this.RemoveUserCredentialCommand = new DelegateCommand<object>(p => this.RemoveUserExecute(), p => this.CanExecuteRemoveCredentials());
        }

        private bool CanExecuteDelete()
        {
            if (this.EditableUserModel != null)
            {
                return this.EditableUserModel.NonVirtualUser;
            }

            return false;
        }

        /// <summary>
        /// Enables/Disables the Remove credential button 
        /// </summary>
        /// <returns>true if there atleast one credential, false otherwise</returns>
        private bool CanExecuteRemoveCredentials()
        {
            return this.EditableUserModel != null && this.EditableUserModel.Credentials != null && this.EditableUserModel.Credentials.Count > 0;
        }

        /// <summary>
        /// Enables/Disables the Remove user button 
        /// </summary>
        /// <returns>true if there atleast one user, false otherwise</returns>
        private void RemoveUserExecute()
        {
            if (this.SelectedCredentials != null)
            {
                if (this.EditableUserModel.Credentials.Contains(this.SelectedCredentials))
                {
                    this.EditableUserModel.Credentials.Remove(this.SelectedCredentials);
                    this.RemoveUserCredentialCommand.RaiseCanExecuteChanged();
                }
            }
        }

        /// <summary>
        /// Saves the changes.
        /// </summary>
        /// <returns>True if save is success.</returns>
        public SaveStateHandler SaveChanges()
        {
            return this.EnforceSaveForChanges();
        }

        #region IRegistryManagerCenterPanePresenter Members

        public UIElement ContentView
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the refresh command.
        /// </summary>
        /// <value>The refresh command.</value>
        public DelegateCommand<BaseRegMgrModel> RefreshCommand { get; set; }

        /// <summary>
        /// Gets or sets the editable category model.
        /// </summary>
        /// <value>The editable category model.</value>
        public UserViewModel EditableUserModel
        {
            get
            {
                return this.editableUserModel;
            }

            set
            {
                this.editableUserModel = value;
                this.OnNotifyPropertyChanged("EditableUserModel");
            }
        }

        /// <summary>
        /// Gets or sets the selected model.
        /// </summary>
        /// <value>The selected model.</value>
        public BaseRegMgrModel SelectedModel
        {
            get
            {
                return this.selectedModel;
            }
            set
            {
                if (null != this.EditableUserModel && !this.EditableUserModel.Id.Equals(value.Id))
                {
                    this.container.Resolve<IStatusMessageControl>().SetText(string.Empty);
                }
                this.selectedModel = value as UserViewModel;
                this.GetAllCredentials();
                this.GetUserGroups();
                this.GetAllRoles();

                this.EditableUserModel = UserViewModel.CreateClone(this.selectedModel);

                this.DeleteUserCommand.RaiseCanExecuteChanged();
                this.RemoveUserCredentialCommand.RaiseCanExecuteChanged();
            }
        }

        #endregion

        private void GetAllCredentials()
        {
            this.selectedModel.Credentials = new ObservableCollection<LogOnCredentialModel>();
            SR.User user = null;
            if (!this.SelectedModel.Id.Equals(Guid.Empty))
            {
                try
                {
                    user = SR.User.Load(this.selectedModel.Id, this.registryManagerService.RegistryConnection);
                }
                catch (SR.RecordNotFoundException)
                {
                    return;
                }

                foreach (SR.ISecurityCredential credential in ((SR.ISecurityPrincipal)user).Credentials)
                {
                    LogOnCredentialModel credentialModel = new LogOnCredentialModel(credential.Credential, string.Empty);
                    this.selectedModel.Credentials.Add(credentialModel);
                }

            }
        }

        private void GetUserGroups()
        {
            this.GetAllDefaultGroups();
            this.GetUsersReferredGroup();
        }

        private void GetAllDefaultGroups()
        {
            this.selectedModel.Groups = new ObservableCollection<CredentialGroupModel>();
            Collection<SR.Group> groups = this.registryManagerService.GetAllActiveGroups();
            foreach (SR.Group group in groups)
            {
                CredentialGroupModel credentialModel = new CredentialGroupModel(group.Name, false);
                this.selectedModel.Groups.Add(credentialModel);
            }
        }

        private void GetUsersReferredGroup()
        {
            SR.User user = null;
            if (!this.selectedModel.Id.Equals(Guid.Empty))
            {
                try
                {
                    user = SR.User.Load(this.selectedModel.Id, this.registryManagerService.RegistryConnection);
                }
                catch (SR.RecordNotFoundException)
                {
                    return;
                }
                //SR.Group gp = new Microsoft.Research.eResearch.Group();
                foreach (SR.Group gp in user.Groups)
                {
                    CredentialGroupModel credentialModel = this.selectedModel.Groups.FirstOrDefault(p => p.GroupName.Equals(gp.Name, System.StringComparison.OrdinalIgnoreCase));
                    if (credentialModel != null)
                    {
                        credentialModel.IsSelected = true;
                    }
                }
            }
        }


        private void GetAllRoles()
        {
            this.selectedModel.Roles = new ObservableCollection<CredentialRoleModel>();
            this.GetAllDefaultRoles();
            this.GetActiveRoles();
        }


        private void GetAllDefaultRoles()
        {
            SR.SystemRoleType rolesEnums = new SR.SystemRoleType();
            string[] roleNames = Enum.GetNames(rolesEnums.GetType());
            foreach (string role in roleNames)
            {
                CredentialRoleModel roleModel = new CredentialRoleModel(role, false);
                this.selectedModel.Roles.Add(roleModel);
            }
        }


        private void GetActiveRoles()
        {
            SR.User user = null;
            if (!this.selectedModel.Id.Equals(Guid.Empty))
            {
                try
                {
                    user = SR.User.Load(this.selectedModel.Id, this.registryManagerService.RegistryConnection);
                }
                catch (SR.RecordNotFoundException)
                {
                    return;
                }
                foreach (SR.SystemRole role in user.SystemRoles)
                {
                    CredentialRoleModel roleModel = this.selectedModel.Roles.FirstOrDefault(p => p.RoleName.Equals(UserCenterPanePresenter.GetRoleName(role.Role)));
                    if (roleModel != null)
                    {
                        roleModel.RoleSelected = true;
                    }
                }
            }
        }

        public static string GetRoleName(SR.SystemRoleType role)
        {
            return Enum.GetName(role.GetType(), role);
        }

        /// <summary>
        /// Enforces whether Save is required for the Model 
        /// </summary>
        /// <returns>SaveStateHolder</returns>
        private SaveStateHandler EnforceSaveForChanges()
        {
            SaveStateHandler saveSuccess = new SaveStateHandler(SaveStatus.Valid);
            if (!this.saveInProgress &&
                this.EditableUserModel != null && !this.editableUserModel.IsDeleted
                && !this.EditableUserModel.Equals(this.SelectedModel as UserViewModel))
            {
                MessageBoxResult userResult = TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(ManagementStudioResourceManager.GetString("SaveChangesToUser"));
                if (userResult == MessageBoxResult.Yes)
                {
                    this.SaveUserCommand.Execute(new object[] { false, saveSuccess });
                }
                else
                {
                    this.Rollback();
                }
            }

            return saveSuccess;
        }

        /// <summary>
        /// Handler Cancel command
        /// </summary>
        private void CancelUserChangesExecute()
        {
            this.Rollback();
            if (this.SelectRootCommand != null)
            {
                this.SelectRootCommand.Execute(null);
            }
        }

        /// <summary>
        /// Roll back all changes.
        /// </summary>
        private void Rollback()
        {
            if (this.SelectedModel != null)
            {
                this.EditableUserModel = UserViewModel.CreateClone(this.SelectedModel as UserViewModel);
            }
        }

        /// <summary>
        /// Handler for Add User command
        /// </summary>
        private void AddUserExecute()
        {
            // Save the current user.
            SaveStateHandler saveState = this.EnforceSaveForChanges();
            if (saveState.SaveOptionSelected == SaveStatus.Valid)
            {
                UserViewModel baseUser = null;
                if (this.selectedModel.Parent != null)
                {
                    baseUser = UserViewModel.CreateClone((UserViewModel)this.selectedModel.Parent);
                }
                else
                {
                    baseUser = UserViewModel.CreateClone((UserViewModel)this.selectedModel);
                }

                this.SelectedModel = new UserViewModel();
                this.EditableUserModel.Parent = baseUser;
                this.EditableUserModel.NonVirtualUser = true;
                this.RemoveUserCredentialCommand.RaiseCanExecuteChanged();
            }

        }

        /// <summary>
        /// Handler for Delete Workflow command
        /// </summary>
        private void AddUserCredentialCommandExecute()
        {
            AddCredentialPresenter addUserCredentials = new AddCredentialPresenter(this.container);
            addUserCredentials.ShowCommand.Execute(CredentialType.User);
            if (addUserCredentials.Credential != null)
            {
                LogOnCredentialModel loginCredential = new LogOnCredentialModel(addUserCredentials.Credential.Name, addUserCredentials.Credential.DisplayName);

                LogOnCredentialModel searchModel = this.EditableUserModel.Credentials.FirstOrDefault(p => p.CredentialName.ToLower(CultureInfo.CurrentCulture).Equals(loginCredential.CredentialName.ToLower(CultureInfo.CurrentCulture)));
                if (searchModel == null)
                {
                    this.EditableUserModel.Credentials.Add(loginCredential);
                    this.RemoveUserCredentialCommand.RaiseCanExecuteChanged();
                }
                else
                {
                    TridentMessageBox.ShowTridentInformationMessageBox(ManagementStudioResourceManager.GetString("ErrorCredentialExist"));
                }
            }
        }

        /// <summary>
        /// Checks wether the user has been deleted
        /// </summary>
        /// <returns></returns>
        private bool IsUserAlreadyDeleted()
        {
            bool userDeleted = false;
            // Load the deleted user.
            if (!this.EditableUserModel.Id.Equals(Guid.Empty))
            {
                SR.User deleteUser = SR.User.Load(this.EditableUserModel.Id, this.registryManagerService.RegistryConnection);
                if (deleteUser != null && deleteUser.IsDeleted)
                {
                    this.editableUserModel.IsDeleted = true;
                    userDeleted = true;
                    if (this.DeleteCommand != null)
                    {
                        this.DeleteCommand.Execute(this.SelectedModel);
                    }

                }
            }

            return userDeleted;
        }

        /// <summary>
        /// Handler for Delete Workflow command
        /// </summary>
        private void DeleteUserCommandExecute()
        {
            try
            {
                if (this.editableUserModel.Id.Equals(((SR.IObject)TridentAuthentication.LoggedInUser).ID))
                {
                    throw new TridentCustomException(ManagementStudio.ErrorCannotDeleteLoggedInUser);
                }

                if (!this.IsUserAlreadyDeleted())
                {
                    MessageBoxResult res = TridentMessageBox.ShowTridentConfirmationYesNoMessageBox(ManagementStudioResourceManager.GetString("DeleteUserConfirmation"));
                    if (res == MessageBoxResult.Yes)
                    {

                        SelectUserPresenter addUserCredentials = new SelectUserPresenter(this.EditableUserModel.Id, this.container, this.registryManagerService);
                        addUserCredentials.ShowCommand.Execute(null);
                        if (addUserCredentials.SelectUserModel != null)
                        {
                            this.registryManagerService.DeleteUser(addUserCredentials.SelectUserModel.Id, this.editableUserModel.Id);
                            this.editableUserModel.IsDeleted = true;
                            if (this.DeleteCommand != null)
                            {
                                this.DeleteCommand.Execute(this.SelectedModel);
                            }
                        }
                    }
                }
                else
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("UserAlreadyDeleted"));
                }
            }
            catch (TridentCustomException ex)
            {
                TridentErrorHandler.HandleTridentCustomExceptionInUI(ex);
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
            }
        }

        /// <summary>
        /// Handler for Save Command
        /// </summary>
        /// <param name="parameters"></param>
        private void SaveUserChangesExecute(object[] parameters)
        {
            this.saveInProgress = true;
            bool saveSucceeded = true;
            SR.User savedUser = null;

            try
            {
                if (!this.IsUserAlreadyDeleted())
                {
                    saveSucceeded = ValidateSave(saveSucceeded);

                    if (saveSucceeded)
                    {
                        this.EditableUserModel.Name = this.EditableUserModel.Name.Trim();
                        saveSucceeded = this.registryManagerService.SaveUser(this.EditableUserModel, out savedUser);
                    }
                    else
                    {
                        TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("UserDataEnteredInvalid"));
                        saveSucceeded = false;
                    }
                }
                else
                {
                    TridentMessageBox.ShowTridentErrorMessageBox(ManagementStudioResourceManager.GetString("UserAlreadyDeleted"));
                    saveSucceeded = false;
                }
            }
            catch (TridentCustomException ex)
            {
                if (ex.ErrorNumber.Equals(TridentErrorConstants.ErrorNumber1000000076.ToString(CultureInfo.CurrentCulture)))
                {
                    TridentMessageBox.ShowTridentWarningMessageBox(ex.Message);
                    TridentErrorHandler.HandleUIException(ex);
                    saveSucceeded = true;
                }
                else
                {
                    TridentErrorHandler.HandleTridentCustomExceptionInUI(ex);
                    saveSucceeded = false;
                }
            }
            catch (Exception ex)
            {
                TridentErrorHandler.HandleKnownExceptionsInUI(ex);
                saveSucceeded = false;
            }

            if (saveSucceeded)
            {
                if (savedUser != null)
                {
                    this.EditableUserModel.Update(savedUser);
                }

                this.EditableUserModel.IsSelected = true;
                this.DeleteUserCommand.RaiseCanExecuteChanged();

                if (this.UpdateCommand != null)
                {
                    this.UpdateCommand.Execute(this.EditableUserModel);
                    this.EditableUserModel = UserViewModel.CreateClone(this.SelectedModel as UserViewModel);
                }

                this.container.Resolve<IStatusMessageControl>().SetText(ManagementStudioResourceManager.GetString("SaveSuccessUser"));
            }
            if (parameters != null && parameters.Length > 1)
            {
                SaveStateHandler saveSuccess = parameters[1] as SaveStateHandler;
                saveSuccess.SaveOptionSelected = saveSucceeded ? SaveStatus.Valid : SaveStatus.Invalid;
                if (!saveSucceeded && !this.SelectedModel.IsSelected)
                {
                    UserViewModel backUpModel = UserViewModel.CreateClone(this.EditableUserModel);
                    this.Rollback();
                    this.SelectedModel.IsSelected = true;
                    this.EditableUserModel = UserViewModel.CreateClone(backUpModel);
                }
            }
            this.saveInProgress = false;
        }

        /// <summary>
        /// Checks the model is valid for save
        /// </summary>
        /// <param name="saveSucceeded">saveSucceeded</param>
        /// <returns>true if model is valid false other wise</returns>
        private bool ValidateSave(bool saveSucceeded)
        {


            if (string.IsNullOrEmpty(this.editableUserModel.Name))
            {
                saveSucceeded = false;
            }
            else if (string.IsNullOrEmpty(this.editableUserModel.Name.Trim()))
            {
                saveSucceeded = false;
            }
            else if (!TridentWorkflowModel.CheckNameForInvalidCharacters(this.editableUserModel.Name.Trim()))
            {
                saveSucceeded = false;
            }



            if (this.editableUserModel.Credentials.Count == 0)
            {
                saveSucceeded = false;
            }

            // Check is there any roles has been checked
            CredentialRoleModel roleModel = this.editableUserModel.Roles.FirstOrDefault(p => p.RoleSelected == true);
            // Check is there any roles has been checked
            CredentialGroupModel groupModel = this.editableUserModel.Groups.FirstOrDefault(p => p.IsSelected == true);

            if (saveSucceeded && (roleModel == null && groupModel == null))
            {
                saveSucceeded = false;
                throw new TridentCustomException(ManagementStudio.UserGroupRoleValidateError);
            }


            List<SR.UserCredential> allCredentials = SR.UserCredential.LoadAll(this.registryManagerService.RegistryConnection);

            foreach (LogOnCredentialModel model in this.editableUserModel.Credentials)
            {
                SR.UserCredential userCredential = allCredentials.FirstOrDefault(p => p.User.IsDeleted == false
                    && p.Credential.ToLower(CultureInfo.CurrentCulture).Equals(model.CredentialName.ToLower(CultureInfo.CurrentCulture)));
                if (userCredential != null && !((SR.IObject)userCredential.User).ID.Equals(this.editableUserModel.Id))
                {
                    if (userCredential.User != null && !userCredential.User.IsDeleted)
                    {
                        saveSucceeded = false;
                        throw new TridentCustomException(string.Format(CultureInfo.CurrentCulture, ManagementStudioResourceManager.GetString("ErrorCredentialExistInRegistry"), model.CredentialName));
                    }
                }
            }

            return saveSucceeded;
        }



        #region INotifyPropertyChanged Members

        /// <summary>
        /// Occurs when a property value changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Called by a property when it wants to notify that its value has changed.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        private void OnNotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}